package leo.mystudy.pojo;

import com.google.common.collect.Lists;
import com.google.common.collect.Streams;
import javafx.util.Pair;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description
 * @Date 2022/5/20 10:56
 * @Author leo
 */
public class Sudoku {
    private static final Integer[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    private final Integer[][] arr = new Integer[9][9];
    ;
    private int nullCount = 0;

    public Sudoku(List<Integer> list) {
        int index = 0;
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (index >= list.size()) {
                    break;
                }
                Integer num = list.get(index++);
                if (num == null) {
                    nullCount++;
                }
                arr[i][j] = num;
            }
        }
    }

    public void decrypt() {
        int count = 0;
        int operateCount;
        main:
        do {
            // 从位置出发，找可以确定填入的数字
            operateCount = 0;
            System.out.printf("第%d次遍历%n", count + 1);
            for (int i = 0; i < 9; i++) {
                for (int j = 0; j < 9; j++) {
                    if (arr[i][j] == null) {
                        // 得到当前位置可以填的数字集合
                        List<Integer> list = findHitNums(i, j);
                        if (list.size() == 1) {
                            setNum(i, j, list.get(0));
                            if (nullCount <= 0) {
                                break main;
                            }
                            operateCount++;
                        }
                    }
                }
            }
            count++;
        } while (operateCount > 0);
    }

    private void setNum(int i, int j, int num) {
        arr[i][j] = num;
        System.out.printf("设置:[%s,%s]为%s%n", i + 1, j + 1, arr[i][j]);
        nullCount--;
    }

    private void setPointNum(int frameIndex, int pointIndex, int num) {
        int i = (frameIndex / 3) * 3 + pointIndex / 3;
        int j = (frameIndex * 3) % 9 + pointIndex % 3;
        setNum(i, j, num);
    }

    /**
     * 得到当前位置可以填的数字集合
     *
     * @param i
     * @param j
     * @return
     */
    private List<Integer> findHitNums(int i, int j) {
        List<Integer> list = Lists.newArrayList(nums);
        Set<Integer> set = Streams.concat(getFrameNums(i, j).stream(), getRowNums(i).stream(), getColumnNums(j).stream()).collect(Collectors.toSet());
        list.removeAll(set);
        return list;
    }

    public void decrypt2() {
        // 从框出发，筛选出数字可以填的位置
        int count = 0;
        int operateCount;
        main:
        do {
            operateCount = 0;
            System.out.printf("第%d次遍历%n", count + 1);
            for (int frameIndex = 0; frameIndex < 9; frameIndex++) {
                List<Integer> frameNums = getFrameNums(frameIndex);
                // 得到框内待填写的数字
                List<Integer> numList = Lists.newArrayList(Sudoku.nums);
                numList.removeAll(frameNums);
                // 遍历待填写数字
                for (Integer num : numList) {
                    // 获取为空的位置
                    List<Integer> nullPointList = findNullPointList(frameIndex);
                    int fIndex = frameIndex;
                    List<Integer> collect = nullPointList.stream().filter(pointIndex -> findPointHitNums(fIndex, pointIndex).contains(num)).collect(Collectors.toList());
                    if (collect.size() == 1) {
                        setPointNum(frameIndex, collect.get(0), num);
                        if (nullCount <= 0) {
                            break main;
                        }
                        operateCount++;
                    }
                }
            }
            count++;
        } while (operateCount > 0);
    }


    /**
     * 获取该位置上可以填入的数字集合
     *
     * @param frameIndex 0-8
     * @param pointIndex 0-8
     * @return
     */
    private Set<Integer> findPointHitNums(int frameIndex, int pointIndex) {
        int i = (frameIndex / 3) * 3 + pointIndex / 3;
        int j = (frameIndex * 3) % 9 + pointIndex % 3;
        return new HashSet<>(findHitNums(i, j));
    }


    private List<Integer> findNullPointList(int frameIndex) {
        int rowStart = (frameIndex / 3) * 3;
        int columnStart = (frameIndex * 3) % 9;
        int index = 0;
        List<Integer> list = new ArrayList<>();
        for (int i = rowStart; i < rowStart + 3; i++) {
            for (int j = columnStart; j < columnStart + 3; j++) {
                if (arr[i][j] == null) {
                    list.add(index);
                }
                index++;
            }
        }
        return list;
    }


    private List<Integer> getRowNums(int row) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 9; i++) {
            if (arr[row][i] != null) {
                list.add(arr[row][i]);
            }
        }
        return list;
    }

    private List<Integer> getColumnNums(int column) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 9; i++) {
            if (arr[i][column] != null) {
                list.add(arr[i][column]);
            }
        }
        return list;
    }

    /**
     * 获取同框内的数字
     *
     * @param i
     * @param j
     * @return
     */
    private List<Integer> getFrameNums(int i, int j) {
        int r = i / 3;
        int c = j / 3;
        List<Integer> list = new ArrayList<>();
        for (int m = r * 3; m < (r + 1) * 3; m++) {
            for (int n = c * 3; n < (c + 1) * 3; n++) {
                if (arr[m][n] != null) {
                    list.add(arr[m][n]);
                }
            }
        }
        return list;
    }

    /**
     * @param frameIndex 0-8
     * @return
     */
    private List<Integer> getFrameNums(int frameIndex) {
        int i = (frameIndex / 3) * 3;
        int j = (frameIndex * 3) % 9;
        return getFrameNums(i, j);
    }

    public void print() {
        for (int i = 0; i < 9; i++) {
            StringBuilder sb = new StringBuilder();
            for (int j = 0; j < 9; j++) {
                sb.append(Optional.ofNullable(arr[i][j]).map(Object::toString).orElse("*"));
                if (j % 3 == 2) {
                    sb.append("|");
                } else {
                    sb.append(" ");
                }
            }
            System.out.println(sb);
            if (i % 3 == 2) {
                System.out.println("——————————————————");
            }
        }
    }

    private static Integer convert(String s) {
        if (s.equals(".")) {
            return null;
        }
        return Integer.parseInt(s);
    }

    private static Sudoku buildSudoku(String filePath) throws IOException {
        List<Integer> nums = new ArrayList<>();
        List<String> list = FileUtils.readLines(new File("C:\\Users\\Yalla\\Documents\\temp.txt"));
        for (String s : list) {
            int start = s.indexOf("|");
            int end = s.lastIndexOf("|");
            if (start >= 0) {
                String line = s.substring(start, end).replaceAll("\\||", "").trim();
                String[] lineArr = line.split("\\s+");
                nums.addAll(Arrays.stream(lineArr).map(Sudoku::convert).collect(Collectors.toList()));
            }
        }
        return new Sudoku(nums);
    }

    public static void main(String[] args) throws IOException {
        Sudoku sudoku = buildSudoku("C:\\Users\\Yalla\\Documents\\temp.txt");

        sudoku.decrypt2();
        sudoku.print();
//        Sudoku sudoku = new Sudoku(Lists.newArrayList(
//                null, null, 3, 7, null, 6, 5, null, null,
//                null, 5, 8, 3, null, null, null, 9, null,
//                1, null, null, null, 9, null, null, 8, 6,
//                5, null, null, null, 1, null, null, 4, 3,
//                null, null, 1, 4, null, 3, 7, null, null,
//                4, 3, null, null, 2, null, null, null, 1,
//                7, 4, null, null, 3, null, null, null, 9,
//                null, 1, null, null, null, 4, 6, 7, null,
//                null, null, 9, 1, null, 2, 4, null, null));
//        sudoku.decrypt();
//        sudoku.print();
    }
}
